
/*
This file is part of [ahttpserver] project. 

Author: Artem Kustikov (kustikoff[at]tut.by)

This code is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this code.

Permission is granted to anyone to use this code for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:

1. The origin of this code must not be misrepresented; you must
not claim that you wrote the original code. If you use this
code in a product, an acknowledgment in the product documentation
would be appreciated but is not required.

2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original code.

3. This notice may not be removed or altered from any source
distribution.


ode based on following document:

		FastCGI Specification
		Mark R. Brown
		Open Market, Inc.

		Document Version: 1.0
		29 April 1996

Some code logic was copied from Open Market FastCGI C++ library (fcgi-2.4.0).
To avoid licensing issues attached original license file - "LICENSE.TERMS".
*/


#include <assert.h>
#include <memory.h> 
#include <string.h>

#include "aconnect/util.hpp"

#include "fastcgi_util.hpp"

namespace fastcgi 
{	
	//==================================================================//
	//	Stream	definition												//
	//==================================================================//
	Stream::Stream (SocketDescriptor sd, size_t bufferSize, int selectTimeout) 
		: _sd (sd),
		_selectTimeout(selectTimeout),
		_bufferSize(bufferSize)
	{
		assert (sd.sock != INVALID_SOCKET);
		
		_buffer = new byte_type[bufferSize];
		_nextPos = _buffer; 
		_currentPos = _buffer;
	}


	Stream::~Stream () 
	{
		delete _buffer;
		_buffer = 0;
	}
	
	//==================================================================//
	//	InputStream	definition											//
	//==================================================================//

	InputStream::InputStream (SocketDescriptor sd, size_t bufferSize, int selectTimeout) 
		: Stream (sd, bufferSize, selectTimeout)
	{
		
	}

	InputStream::~InputStream()
	{
	}

	bool InputStream::readHeader (Header &head) throw (fastcgi_error)
	{
		size_t read = readBlock ((byte_type*) &head, HeaderLenght);
		if (read == 0)
			return false;

		if (HeaderLenght != (int) read)
			throw fastcgi_error(FcgiError::ProtocolError, "FastCGI header corrupted.");

		return true;
	}

	size_t InputStream::readByte (byte_type& byte) throw (fastcgi_error)
	{
		if (0 == getAvailableSize()) 
		{
			fillBuffer();
			if (0 == getAvailableSize())
				return 0;
		}
		
		byte = *_currentPos++;
		return 1;
	}

	size_t InputStream::readBlock (byte_type* buff, size_t length)  throw (fastcgi_error)
	{
		size_t availSize = getAvailableSize();

		if (length <= availSize) {
			
			memcpy(buff, _currentPos, length);
			_currentPos += length;
			return length;
		}

		size_t remainLength = length;

		while (remainLength > 0) {
						
			if (availSize > 0) {
				
				size_t bytesToCopy = aconnect::util::min2 (remainLength, availSize);
				
				memcpy (buff, _currentPos, bytesToCopy);

				buff += bytesToCopy;
				_currentPos += bytesToCopy;
				remainLength -= bytesToCopy;
			}

			if (remainLength > 0) {
				fillBuffer();
				availSize = getAvailableSize();
				
				if (availSize == 0) // data is not available
					break;
			}
		}

		return (length - remainLength);
	}
	
	aconnect::string InputStream::read (size_t length) throw (fastcgi_error)
	{
		size_t availSize = getAvailableSize();

		if (length <= availSize) {
			
			aconnect::string res = aconnect::string((aconnect::string::value_type*) _currentPos, length);
			_currentPos += length;

			return res;
		}

		aconnect::str_stream response;

		size_t remainLength = length;

		while (remainLength > 0) {
						
			if (availSize > 0) {
				
				size_t bytesToCopy = aconnect::util::min2 (remainLength, availSize);
				
				response << aconnect::string((aconnect::string::value_type*) _currentPos, bytesToCopy);

				_currentPos += bytesToCopy;
				remainLength -= bytesToCopy;
			}

			if (remainLength > 0) {
				fillBuffer();
				availSize = getAvailableSize();
				
				if (availSize == 0) // data is not available
					break;
			}
		}

		return response.str();
	}
	
	
	void InputStream::fillBuffer () throw (fastcgi_error)
	{
		size_t readDataLen = (_currentPos - _buffer);

		if (readDataLen > _bufferSize / 2) 
		{
			const size_t dataSize = _nextPos - _currentPos;

			// move data from end to begin
			if (dataSize > 0)
				memcpy (_buffer, _currentPos, dataSize);

			_nextPos = _buffer + dataSize;
			_currentPos = _buffer;

		}
		
		size_t readBytes = ApplicationManager::read (_sd, 
			_nextPos, 
			_bufferSize - (_nextPos - _buffer), 
			_selectTimeout );
		
		assert ((_nextPos - _buffer) + readBytes < _bufferSize);
		_nextPos += readBytes;


	}
}
